package com.net.xpay.backend.scheduler;


import com.google.common.base.Strings;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.net.common.util.HttpClientUtil;
import com.net.common.util.SerializationUtil;
import com.net.xpay.common.domain.User;
import com.net.xpay.common.domain.UserProfile;
import com.net.xpay.common.domain.notify.ChargeNotifyRecord;
import com.net.xpay.common.domain.openapi.OpenApp;
import com.net.xpay.common.enums.notify.NotifyStatus;
import com.net.xpay.common.manager.UserManager;
import com.net.xpay.common.manager.UserProfileManager;
import com.net.xpay.common.manager.notify.ChargeNotifyRecordManager;
import com.net.xpay.common.manager.openapi.OpenAppManager;
import com.net.xpay.common.model.search.ChargeNotifyRecordSearchModel;
import com.net.xpay.common.util.OpenApiSignatureUtil;
import com.net.xpay.core.helper.SignerCheckHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * 2019/7/15 16:28

 * 开放的充值回调通知
 */
@Slf4j
@Component
public class ChargeNotifyRecordScheduler {

    @Autowired
    private ChargeNotifyRecordManager chargeNotifyRecordManager;

    @Autowired
    private UserProfileManager userProfileManager;

    @Autowired
    private UserManager userManager;

    @Autowired
    private OpenAppManager openAppManager;

    @Autowired
    private SignerCheckHelper signerCheckHelper;

    private ExecutorService executorService = Executors.newFixedThreadPool(4, new ThreadFactoryBuilder().setNameFormat("ChargeNotifyRecordScheduler-%d").build());

    /**
     * 清理过期的数据
     */
    @Scheduled(cron = "0 0 2 * * *")
    public void cleanChargeNotifyRecord() {
        log.info("cleanChargeNotifyRecord start");

        //只保留一个月
        LocalDateTime deadline = LocalDateTime.now().minusMonths(1L);
        chargeNotifyRecordManager.deleteFinishedRecord(deadline);

        log.info("cleanChargeNotifyRecord end");
    }

    /**
     * 查询交易状态
     */
    @Scheduled(cron = "*/1 * * * * *")
    public void notifyChargeRecord() {
        ChargeNotifyRecordSearchModel searchModel = new ChargeNotifyRecordSearchModel();
        searchModel.setStatus(NotifyStatus.WAIT);
        searchModel.setMaxNextNotifyTime(LocalDateTime.now());
        List<ChargeNotifyRecord> records = chargeNotifyRecordManager.search(searchModel);

        if (CollectionUtils.isEmpty(records)) {
            return;
        }
        List<FutureTask<Void>> tasks = new ArrayList<>();
        for (ChargeNotifyRecord record : records) {
            FutureTask<Void> futureTask = new FutureTask<>(() -> {
                if (record.getAppId() == null) {
                    //非开放应用
                    doNotifyChargeRecord(record);
                } else {
                    //开放应用
                    doOpenApiNotifyChargeRecord(record);
                }
            }, null);
            executorService.submit(futureTask);
            tasks.add(futureTask);
        }
        for (FutureTask<Void> task : tasks) {
            try {
                task.get();
            } catch (Exception e) {
                log.error("notifyChargeRecord error", e);
            }
        }
    }

    private void doOpenApiNotifyChargeRecord(ChargeNotifyRecord record) {
        UserProfile userProfile = userProfileManager.getById(record.getUserId());
        if (Strings.isNullOrEmpty(userProfile.getChargeNotifyUrl())) {
            log.error("doOpenApiNotifyChargeRecord error|user no profile chargeNotifyUrl|profile={}", SerializationUtil.obj2String(userProfile));
            return;
        }

        OpenApp openApp = openAppManager.getById(record.getAppId());
        String time = System.currentTimeMillis() + "";
        Map<String, String> params = new HashMap<>();
        params.put("appId", record.getAppId().toString());
        params.put("timestamp", time);
        params.put("data", record.getNotifyContent());
        String content = OpenApiSignatureUtil.getSignCheckContent(params);
        String sign = OpenApiSignatureUtil.rsaSign(content, openApp.getPrivateKey(), "UTF-8", "RSA2");

        List<NameValuePair> paramList = new ArrayList<>();
        NameValuePair appId = new BasicNameValuePair("appId", record.getAppId().toString());
        paramList.add(appId);
        NameValuePair timestamp = new BasicNameValuePair("timestamp", time);
        paramList.add(timestamp);
        NameValuePair signature = new BasicNameValuePair("sign", sign);
        paramList.add(signature);
        NameValuePair data = new BasicNameValuePair("data", record.getNotifyContent());
        paramList.add(data);

        String resp;
        try {
            HttpPost method = new HttpPost(userProfile.getChargeNotifyUrl());
            method.setEntity(new UrlEncodedFormEntity(paramList, "UTF-8"));
            log.info("doOpenApiNotifyChargeRecord|url={}|id={}|appId={}|userId={}|params={}", userProfile.getChargeNotifyUrl(), record.getImportBatchNo(), record.getAppId(), record.getUserId(), SerializationUtil.obj2String(paramList));
            resp = HttpClientUtil.getInstance().executeAndGet(method, null, "UTF-8");
            log.info("doOpenApiNotifyChargeRecord|url={}|id={}|appId={}|userId={}|resp={}", userProfile.getChargeNotifyUrl(), record.getImportBatchNo(), record.getAppId(), record.getUserId(), resp);
            if (resp != null && (
                    "ok".equalsIgnoreCase(resp.trim())
                            || "\"ok\"".equalsIgnoreCase(resp.trim())
                            || "success".equalsIgnoreCase(resp.trim())
                            || "\"success\"".equalsIgnoreCase(resp.trim())
            )
            ) {
                record.setStatus(NotifyStatus.OK);
                record.setNotifyCount(record.getNotifyCount() + 1);
                record.setEndTime(LocalDateTime.now());
                chargeNotifyRecordManager.update(record);
            } else {
                record.setNotifyCount(record.getNotifyCount() + 1);
                if (record.getNotifyCount() >= 8) {
                    record.setStatus(NotifyStatus.DEAD);
                    record.setEndTime(LocalDateTime.now());
                    chargeNotifyRecordManager.update(record);
                } else {
                    long[] minutes = new long[]{
                            2, 10, 10, 60, 120, 360, 900
                    };
                    record.setNextNotifyTime(record.getNextNotifyTime().plusMinutes(minutes[(record.getNotifyCount() - 1) % 7]));
                    chargeNotifyRecordManager.update(record);
                }
            }
        } catch (Throwable e) {
            log.error("doOpenApiNotifyChargeRecord error|id={}|appId={}|userId={}", record.getImportBatchNo(), record.getAppId(), record.getUserId(), e);
            record.setNotifyCount(record.getNotifyCount() + 1);
            if (record.getNotifyCount() >= 8) {
                record.setStatus(NotifyStatus.DEAD);
                record.setEndTime(LocalDateTime.now());
                chargeNotifyRecordManager.update(record);
            } else {
                long[] minutes = new long[]{
                        2, 10, 10, 60, 120, 360, 900
                };
                record.setNextNotifyTime(record.getNextNotifyTime().plusMinutes(minutes[(record.getNotifyCount() - 1) % 7]));
                chargeNotifyRecordManager.update(record);
            }
        }
    }

    private void doNotifyChargeRecord(ChargeNotifyRecord record) {
        UserProfile userProfile = userProfileManager.getById(record.getUserId());
        if (Strings.isNullOrEmpty(userProfile.getChargeNotifyUrl())) {
            log.error("doNotifyChargeRecord error|user no profile chargeNotifyUrl|profile={}", SerializationUtil.obj2String(userProfile));
            return;
        }
        User user = userManager.getById(record.getUserId());

        List<NameValuePair> paramList = new ArrayList<>();
        NameValuePair appkey = new BasicNameValuePair("appkey", user.getAppkey());
        paramList.add(appkey);
        String time = System.currentTimeMillis() + "";
        NameValuePair timestamp = new BasicNameValuePair("timestamp", time);
        paramList.add(timestamp);
        NameValuePair signature = new BasicNameValuePair("signature", signerCheckHelper.sign(user.getAppkey(), user.getSecret(), time));
        paramList.add(signature);
        NameValuePair data = new BasicNameValuePair("data", record.getNotifyContent());
        paramList.add(data);

        String resp;
        try {
            HttpPost method = new HttpPost(userProfile.getChargeNotifyUrl());
            method.setEntity(new UrlEncodedFormEntity(paramList, "UTF-8"));
            resp = HttpClientUtil.getInstance().executeAndGet(method, null, "UTF-8");
            log.info("doNotifyChargeRecord|url={}|id={}|userId={}|resp={}", userProfile.getChargeNotifyUrl(), record.getImportBatchNo(), record.getUserId(), resp);
            if (resp != null && (
                    "ok".equalsIgnoreCase(resp.trim())
                    || "\"ok\"".equalsIgnoreCase(resp.trim())
                    || "success".equalsIgnoreCase(resp.trim())
                    || "\"success\"".equalsIgnoreCase(resp.trim())
            )
            ) {
                record.setStatus(NotifyStatus.OK);
                record.setNotifyCount(record.getNotifyCount() + 1);
                record.setEndTime(LocalDateTime.now());
                chargeNotifyRecordManager.update(record);
            } else {
                record.setNotifyCount(record.getNotifyCount() + 1);
                if (record.getNotifyCount() >= 8) {
                    record.setStatus(NotifyStatus.DEAD);
                    record.setEndTime(LocalDateTime.now());
                    chargeNotifyRecordManager.update(record);
                } else {
                    long[] minutes = new long[]{
                            2, 10, 10, 60, 120, 360, 900
                    };
                    record.setNextNotifyTime(record.getNextNotifyTime().plusMinutes(minutes[(record.getNotifyCount() - 1) % 7]));
                    chargeNotifyRecordManager.update(record);
                }
            }
        } catch (Throwable e) {
            log.error("doNotifyChargeRecord error|id={}|userId={}", record.getImportBatchNo(), record.getUserId(), e);
            record.setNotifyCount(record.getNotifyCount() + 1);
            if (record.getNotifyCount() >= 8) {
                record.setStatus(NotifyStatus.DEAD);
                record.setEndTime(LocalDateTime.now());
                chargeNotifyRecordManager.update(record);
            } else {
                long[] minutes = new long[]{
                        2, 10, 10, 60, 120, 360, 900
                };
                record.setNextNotifyTime(record.getNextNotifyTime().plusMinutes(minutes[(record.getNotifyCount() - 1) % 7]));
                chargeNotifyRecordManager.update(record);
            }
        }

    }
}
